﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: BernoulliDistribution.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;

namespace BOL.Maths.Distributions
{
    /// <summary>
    /// Represents a Bernoulli distribution with probability parameter p.
    /// <see>Mathematica: ref/BernoulliDistribution</see>
    /// <see>http://en.wikipedia.org/wiki/Bernoulli_distribution</see>
    /// <typeparam name="TKey"></typeparam>
    /// </summary>
    public sealed class BernoulliDistribution<TKey> : NomialDistributionBase<TKey>, IUnivariateDistribution<TKey>, IEquatable<BernoulliDistribution<TKey>>
        where TKey : struct, IComparable, IComparable<TKey>, IEquatable<TKey>
    {
        #region Private variables

        private double _p; // probability of outcome 1
        private double _q; // probability of outcome 2;

        #endregion

        #region Public properties

        /// <summary>Gets the range of a Bernoulli distribution.</summary>
        public IRange<TKey> Domain { get { return new Range<TKey>(ProbabilityTable.Keys.First(), ProbabilityTable.Keys.Last()); } }

        /// <summary>Gets the mean of a Bernoulli distribution.</summary>
        public double Mean { get { return _p; } }

        /// <summary>Gets the median of a Bernoulli distribution.</summary>
        public double Median { get { throw new Exception("Median is undefined for Bernoulli distribution."); } }

        /// <summary>Gets the mode of a Bernoulli distribution.</summary>
        public double Mode { get { return _q > _p ? 0 : 1; } }

        /// <summary>Gets the variance of a Bernoulli distribution.</summary>
        public double Variance { get { return _p * _q; } }

        /// <summary>Gets the skewness of a Bernoulli distribution.</summary>
        public double Skewness { get { return (_q - _p) / Math.Sqrt(_p * _q); } }

        /// <summary>Gets the kurtosis of a Bernoulli distribution.</summary>
        public double Kurtosis { get { return (6 * _p * _p - 6 * _p + 1) / (_p * _q); } }

        /// <summary>Gets the entropy of a Bernoulli distribution.</summary>
        public double Entropy { get { return -_q * Math.Log(_q) - _p * Math.Log(_p); } }

        #endregion

        #region Constructor

        /// <summary>
        /// Instantiates a Bernoulli distribution.
        /// </summary>
        /// <param name="probabilityTable">Probability table.</param>
        public BernoulliDistribution(IDictionary<TKey, double> probabilityTable)
            : base(probabilityTable)
        {
            if (ProbabilityTable.Count != 2)
                throw new ArgumentException("Bernoulli distribution can have two outcomes", "probabilityTable");

            _p = ProbabilityTable.ElementAt(0).Value;
            _q = ProbabilityTable.ElementAt(1).Value;
        }

        public BernoulliDistribution(TKey success, TKey fail)
            : this(new Dictionary<TKey, double> { { success, 0.5 }, { fail, 0.5 } }) { }

        #endregion

        #region INomialDistribution<TKey> implementation

        /// <summary>Gets or sets the frequency table of a Bernoulli distribution.</summary>
        public override IDictionary<TKey, double> ProbabilityTable
        {
            get { return base.ProbabilityTable; }
            set
            {
                base.ProbabilityTable = value;

                if (value.Count != 2)
                    throw new ArgumentException("Bernoulli distribution can have two outcomes", "value");

                _p = ProbabilityTable.ElementAt(0).Value;
                _q = ProbabilityTable.ElementAt(1).Value;
            }
        }

        #endregion

        #region ICloneable implementation

        public BernoulliDistribution<TKey> Clone()
        {
            return new BernoulliDistribution<TKey>(ProbabilityTable);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }
        
        #endregion

        #region Public methods

        /// <summary>
        /// Returns the Bernoulli density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>Bernoulli density</returns>
        public double Pdf(TKey value)
        {
            return !ProbabilityTable.ContainsKey(value) ? 0 : ProbabilityTable[value];
        }

        /// <summary>
        /// Returns the cummulative Bernoulli density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>cummulative Bernoulli density</returns>
        public double Cdf(TKey value)
        {
            return ProbabilityTable.ContainsKey(value) ? Pdf(value) : 0;
        }

        /// <summary>
        /// Returns the value of the Bernoulli distribution for given probability.
        /// </summary>
        /// <param name="p">probability</param>
        /// <returns></returns>
        public TKey Quantile(double p)
        {
            return p < _p ? ProbabilityTable.Keys.ElementAt(1) : ProbabilityTable.Keys.ElementAt(0);
        }

        /// <summary>
        /// Returns a random value from the Bernoulli distribution.
        /// <see>http://people.csail.mit.edu/milch/blog/index.html</see>
        /// </summary>
        /// <returns>random value</returns>
        public TKey Sample(Random r)
        {
            return r.NextDouble() < _p ? ProbabilityTable.Keys.ElementAt(1) : ProbabilityTable.Keys.ElementAt(0);
        }

        #endregion

        #region IEquatable<TKey> implementation

        public bool Equals(BernoulliDistribution<TKey> other)
        {
            return base.Equals(other);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return String.Format("Bernoulli({0})", String.Join(", ", ProbabilityTable.Select(p => String.Format("p_{0} = {1:0.0000}", p.Key, p.Value))));
        }

        #endregion
    }
}
